/**
 * @class 宇宙数据代理
 * @author DuskyHuang 圣子
 * @description 提供全局相关数据和管理。
*/

import { Proxy, decorators, Notifications } from 'cfw';
import { Diameters, LOCAL_TO_SOLAR, MILKY_TO_SOLAR, POW_OFS, VIRGO_TO_SOLAR } from 'const';
import { UI, View } from 'notifications';
import { LogProxy } from './log-proxy';
const { singleton } = decorators;
const { SOLAR: S, HELIO: H, MILKY: M, LOCAL: L, VIRGO: V, LANIAKEA: A, HUBBLE: B } = Diameters;
import { visit } from './visit-proxy';

@singleton
export class CosmosProxy extends Proxy {

    declare public static readonly instance: CosmosProxy;
    private constructor() { super(); }

    public static readonly INIT_POW: pow10 = -4;
    public static readonly COSMOS_POWS: [pow10, pow10] = [-9, 11.3];

    public get planetary(): boolean { return this._between(CosmosProxy.Scopes.Planetary); }
    public get solar(): boolean { return this._between(CosmosProxy.Scopes.Solar); }
    public get helio(): boolean { return this._between(CosmosProxy.Scopes.Helio); }
    public get stellar(): boolean { return this._between(CosmosProxy.Scopes.Stellar); }
    public get milky(): boolean { return this._between(CosmosProxy.Scopes.MilkyWay); }
    public get local(): boolean { return this._between(CosmosProxy.Scopes.LocalGG); }
    public get virgo(): boolean { return this._between(CosmosProxy.Scopes.VirgoSC); }
    public get super(): boolean { return this._between(CosmosProxy.Scopes.Observable); }

    public get underSolar(): boolean { return this._pow10 <= CosmosProxy.Scopes.Solar[1] - 0.5; }
    public get upSolar(): boolean { return this._pow10 <= CosmosProxy.Scopes.Solar[1] + 0.5; }
    public get underStellar(): boolean { return this._pow10 <= CosmosProxy.Scopes.Stellar[1] - 0.5; }

    public static readonly Scopes: Record<scalar, scope> = {
        Planetary: [-99, -3, -7],
        Specific: [-99, -1, -5],
        Solar: [-99, -1.4, -5, [S / 2, S / 1.6, S / 2]],
        Helio: [-3, 0.7, -3, [H / 2, H / 1.8, H / 2]],
        Stellar: [-99, 4.5, POW_OFS],
        MilkyWay: [1.5, 7, 4 + POW_OFS, [M / 2, M, M / 2], MILKY_TO_SOLAR],
        LocalGG: [4.5, 8, 6 + POW_OFS, [L / 2, L * 0.8, L / 2], LOCAL_TO_SOLAR],
        VirgoSC: [6, 9.2, 8 + POW_OFS, [V / 2, V / 1.8, V / 1.2], VIRGO_TO_SOLAR],
        Laniakea: [6, 11, 8 + POW_OFS, [A / 2, A / 2, A / 2]],
        Observable: [7.5, 12 + POW_OFS, 8, [B / 2, B, B / 2]]
    };

    public static readonly Bases: Record<scalar, pow10> = {
        Planetary: -7, Specific: -3.0, Solar: -3.0,
        Helio: -0.3, Stellar: 2.3, MilkyWay: 4.9,
        LocalGG: 6.8, VirgoSC: 7.9, Laniakea: 8.6,
        Observable: 11.25
    };

    public static readonly TRANS_SPEED_ZOOM_UP: pow10 = 1;
    public static readonly TRANS_SPEED_ZOOM_IN: pow10 = 0.5;
    public static readonly TRANS_SPEED_SCOPE: pow10 = 0.55;
    public static readonly TRANS_HIGHT: num.deg = 27;
    public static readonly ROTATE_SPEED: num.deg = 15;

    public get scale(): num.ratio { return this._scale; }
    public get fov(): num.ratio { return this._fov; }
    public get pow10(): pow10 { return this._pow10; }
    public get autoAA2nd(): pow10 { return this._autoAA2nd; }
    public get delta(): pow10 { return this._delta; }   // 当前变化（pow10/s）

    protected init(): void {
        this.on(Notifications.Time.TICK, this.onTick);
        this.on(UI.Zoom, this.onZoom);
        this.on(View.Scale.RATIO_CHANGE, this.onRatioChanged);
    }

    public addPow10(delta: pow10): void {
        this._dest10 += delta;
        this._pow10 += delta;
        this._syncByDest();
    }

    public zoomToDur(dest: pow10, duration: time.sec, rotate: boolean = null, notifyCompleted: string = null): void {
        this._dest10 = dest;
        this._step = (dest - this._pow10) / (Math.abs(duration) + Number.EPSILON);
        this._ntfComp = notifyCompleted;
        this._syncByDest();
        if (isBool(rotate)) this._autoAA2nd = (rotate ? 1 : -1) * CosmosProxy.ROTATE_SPEED;
    }

    public zoomToBy(dest: pow10, step: pow10, rotate: boolean = null, notifyCompleted: string = null): void {
        this._dest10 = dest;
        this._step = (dest - this._pow10) > 0 ? step : -step;
        this._ntfComp = notifyCompleted;
        this._syncByDest();
        if (isBool(rotate)) this._autoAA2nd = (rotate ? 1 : -1) * CosmosProxy.ROTATE_SPEED;
    }

    protected onTick(dt?: time.sec, tick?: num.int): void {
        this._delta = this._pow10;
        if (this._step == 0) {
            this._pow10 = Math.lerp(this._pow10, this._dest10, dt);
        } else {
            this._pow10 += this._step * dt * TRANS_ACC;
            if (this._step > 0 && this._pow10 >= this._dest10 || this._step < 0 && this._pow10 <= this._dest10) {
                this._pow10 = this._dest10;
                this._step = 0;
                this._ntfComp && this.notify(this._ntfComp);
                this._autoAA2nd = 0;
            }
        }
        this._delta = (this._pow10 - this._delta) / dt;
        if (LogProxy.PRINT_POW10) {
            console.info('[pow10]:', this._pow10);
        }
    }

    private _syncByDest(): void {
        this._fov = 10 ** (this._dest10 - CosmosProxy.INIT_POW);
        this._scale = 1 / this._fov;
    }

    protected onZoom(delta: num.ratio): void {
        const [FLOOR, CEIL] = CosmosProxy.COSMOS_POWS;
        const floor = visit.center?.nearest ?? FLOOR;
        const scale = this._scale * (1 + delta);
        const fov = 1 / scale;
        const dest10 = Math.log10(fov) + CosmosProxy.INIT_POW;
        if (dest10 <= floor || dest10 >= CEIL) return;
        this._scale = scale;
        this._fov = fov;
        this._dest10 = dest10;
    }

    protected onRatioChanged(): void {
        const floor = visit.center?.nearest ?? CosmosProxy.COSMOS_POWS[0];
        if (floor > this._dest10) {
            this._dest10 = floor;
            this._syncByDest();
        }
    }

    private _between(scope: scope): boolean { const [F, T] = scope; return F <= this._pow10 && this._pow10 <= T; }
    private _scale: num.ratio = 1;
    private _fov: num.ratio = 1;
    private _dest10: pow10 = CosmosProxy.INIT_POW;
    private _pow10: pow10 = CosmosProxy.INIT_POW;
    private _autoAA2nd: num.deg = 0;
    private _delta: pow10 = 0;
    private _step: pow10 = 0;
    private _ntfComp: string = null;
}

export const cosmos = CosmosProxy.instance;
const TRANS_ACC: num.ratio = 1;